/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.servlet;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xsm.io.IOUtil;
import com.xsm.io.StreamResult;
import com.xsm.lite.util.Logg;

/**
 * Serves up a GWT App's compiled arificats.
 */
public class GWTStaticContentServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final Logg log = Logg.getLogg("GWTStaticContentServlet");
    
    private static final long AGE_FOREVER = 60*60*24*365*3;
    private static final long AGE_SESSION = 60*60*3;
    
    private static final Map<String, byte[]> gzipContentCache = new HashMap<String, byte[]>();
    
    /**
     * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {      
        super.init(config);
        log.debug("Initialized");
    }

    /**
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    protected void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException {
        String path = ServletUtil.getRequestPath(httpRequest);
        log.debug("Requested=[" + path + "]");
        
        if (path == null || (path=path.trim()).length()==0) {
            return;
        }
        
        InputStream contentStream = null;
        try {            
            boolean gzipable = false;
            
            if (path.endsWith(".nocache.js")) {
                gzipable = false;
                setExpiresAge(httpResponse, 1);
            }else if (path.endsWith(".cache.html") || path.endsWith(".cache.js")) {
                gzipable = true;
                setExpiresAge(httpResponse, AGE_FOREVER);
            }else if (path.endsWith(".js")) {
                gzipable = true;
                setExpiresAge(httpResponse, AGE_SESSION);
            }else{
                gzipable = false;
                setExpiresAge(httpResponse, AGE_SESSION);
            }
                                    
            String contentType = getContentType(path);            
            httpResponse.setContentType(contentType);
                        
            int contentBytes = 0;
            int encodedToBytes = 0;
            String encodeToMsg = "NONE";
            
            String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
            if (gzipable && acceptEncoding != null && (acceptEncoding=acceptEncoding.trim()).contains("gzip")) 
                    //&& (!acceptEncoding.contains("gzip;q=0"))) 
            {                
                byte[] gzipContent = gzipContentCache.get(path);
                if (gzipContent == null) {
                    synchronized(gzipContentCache) {
                        gzipContent = gzipContentCache.get(path);
                        if (gzipContent == null) {                      
                            contentStream = createStream(path);
                            if (contentStream == null) {
                                log.warn("NotFound=[" + path + "]", null);                                
                                httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Not Found");
                                return;
                            }                            
                            StreamResult resultStream = IOUtil.read(contentStream);
                            if (resultStream != null && resultStream.getLength() > 0) {
                                contentBytes = resultStream.getLength();
                                ByteArrayOutputStream gzbs = new ByteArrayOutputStream(resultStream.getLength());
                                GZIPOutputStream gzs = new GZIPOutputStream(gzbs, 1024);
                                resultStream.write(gzs);
                                gzs.finish();
                                gzipContent = gzbs.toByteArray();
                                gzipContentCache.put(path, gzipContent);
                            }
                        }
                    }
                }
                if (gzipContent != null && gzipContent.length > 0) {
                    encodeToMsg = "GZIP";
                    encodedToBytes = gzipContent.length;
                    httpResponse.setHeader("Content-Encoding", "gzip");
                    httpResponse.getOutputStream().write(gzipContent);
                }
            }else{
                contentStream = createStream(path);
                if (contentStream == null) {
                    log.warn("NotFound=[" + path + "]", null);                                
                    httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Not Found");
                    return;
                }
                
                StreamResult resultStream = IOUtil.read(contentStream);
                if (resultStream != null && resultStream.getLength() > 0) {
                    contentBytes = resultStream.getLength();
                    httpResponse.setContentLength(resultStream.getLength());
                    resultStream.write(httpResponse.getOutputStream());
                }
            }
                                    
            httpResponse.getOutputStream().flush();
            log.debug("Resolved=[" + path + "], contentBytes=[" + contentBytes + "] encodedTo=["+encodeToMsg+"], encodedToBytes=[" + encodedToBytes + "]");
        } catch (Throwable x) {
            log.warn("NotFound=[" + path + "]", x);                                
            httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Not Found");
        } finally{
            if(contentStream != null)
                contentStream.close();          
        }
       
    }
        
    /**
     * Sets the appropriate headers for expiring the sent static content.
     * 
     * author Sony Mathew
     */
    private void setExpiresAge(HttpServletResponse resp, long ageInSeconds) {
        resp.setHeader("Cache-Control", "max-age=" + ageInSeconds + ", public");
    }
    
    private void logServletPaths(HttpServletRequest httpRequest) {
        log.debug("ContextPath = " + httpRequest.getContextPath());
        log.debug("ServletPath = " + httpRequest.getServletPath());
        log.debug("PathInfo = " + httpRequest.getPathInfo());
        log.debug("RequestURI = " + httpRequest.getRequestURI());
        log.debug("RequestURL = " + httpRequest.getRequestURL());
                
    }
    
    /**
     * author Sony Mathew
     */
    protected InputStream createStream(String resource) throws IOException, URISyntaxException {        
        URL resURL = getServletContext().getResource(resource);
        if (resURL != null) {
            File resFile = new File(resURL.toURI());
            if (resFile.exists() && !resFile.isDirectory()) {
                return resURL.openStream();
            }
        }
        return null;
    }
    
    /**
     * XXX: Optimize..
     * 
     * author Sony Mathew
     */
    public String getContentType(String resource) {
        resource = resource.toLowerCase();
        if (resource.endsWith(".gif")){
           return "image/gif";
        }else if (resource.endsWith(".jpg") || resource.endsWith(".jpeg")){
           return "image/jpeg";
        }else if (resource.endsWith(".js")){
           return "application/x-javascript";
        }else if (resource.endsWith(".css")){
           return "text/css";
        }else if (resource.endsWith(".html") || resource.endsWith(".htm") || resource.endsWith(".htmx")){
           return "text/html";                 
        }else{
           return "";
        }
    }            
}
